/*****************************************************************************
 * $LastChangedDate: 2010-06-24 10:22:48 -0400 (Thu, 24 Jun 2010) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Base Object class.
 *//*
 * LEGAL:   COPYRIGHT (C) 2007 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define OBJECT_OBJECT_CC 1
#include "base/module.hh"
#include "base/bits.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs.hh"
#include "math/matrix.hh"
#include "math/matrix_funcs.hh"
using namespace math;
#include "graph/module.hh"
#include "graph/scene_graph.hh"
using namespace graph;
#include "world/module.hh"
using namespace world;
#include "collision/module.hh"
using namespace collision;
#include "control/module.hh"
using namespace control;
#include "object/module.hh"
#include "object/object.hh"
#include "object/zombies.hh"
#include "object/events.hh"
using namespace object;

namespace object {

////////////////////////////////////////////////////////////////////////////////
/// @brief Dummy Object.
///
class DummyObject : public Object
{
PREVENT_COPYING(DummyObject)
public:
    DummyObject( void )
    :   Object(Graph::MakeMinimalGraph(),WorldVertex(0,0,0))
    {
        mBits.mDummy = true;
    }

    virtual const string GetName( void ) { return string("DummyObject"); }
};

////////////////////////////////////////////////////////////////////////////////
///////////////////////////////  Object  ///////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

int             Object::msObjectCount = 0;
const NodeSort  Object::msDefaultNodeSort;

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
Object::Object( shptr<Graph> graph, const WorldVertex& pos )
:   mGraph(graph),
    mTransformNode(graph->GetTransformNode()),
    mRadius(-1.0f),  // -1 means uncomputed
    mBits()
{
    ++msObjectCount;
    SET_TYPESIG(this,TYPESIG_OBJECT);
    CLEAR_BIT_FIELDS(&mBits);

    SetPosition( pos );
}

Object::~Object()
{
    // Object dtor detaches from scene-graph even though Object ctor didn't attach.
    GET_SCENE_GRAPH().DetachNode( GetRootNode() );

    --msObjectCount;
    ASSERT( msObjectCount >= 0 );  // catch double-deletion
    INVALIDATE_TYPESIG(this,TYPESIG_OBJECT);
}

/*****************************************************************************
 * Make a dummy Object.
 *****************************************************************************/
shptr<Object>  // CLASS_METHOD
Object::MakeDummyObject( void )
{
    // Re-use the same dummy to minimize Object count.
    PERSISTENT shptr<DummyObject> sDummyObject = new DummyObject;
    return sDummyObject.PTR();
}

/*****************************************************************************
 * Ok to call repeatedly (NOP).
 * Zombification reduces memory consumption by freeing an Object's Graph.
 * Some 3D models consume tens of megabytes (which a Graph holds).
 * Replacing Graph is potentially dangerous but so is manipulating a detached Object.
 *****************************************************************************/
void
Object::Zombify( void )
{
CHECK_OBJECT();

    // Omit calling World::Detach().
    // Let World::Detach() call Object::Zombify().
    // World::Detach() will release the Object's previous Graph.

    // Do once.
    if ( not mBits.mZombie )
    {
        // Set zombie bit.
        mBits.mZombie = true;

        // First detach the current Graph.
        GET_SCENE_GRAPH().DetachNode( GetRootNode() );

        // As step in reducing memory usage, replace Graph with a minimal one.
        mGraph = Graph::MakeMinimalGraph();

        // Schedule this Object for destruction.
        GET_ZOMBIE_OBJECTS().Zombify( this );
    }
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////  Object : drawing  ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Draw Object.
 *****************************************************************************/
void
Object::Draw( void )
{
CHECK_OBJECT();

    // (NOP on OSG)
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////  Object : spatial  ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Set/get position of Object.
 * Position is defined by the matrix origin.
 *****************************************************************************/
void
Object::SetPosition( const WorldVertex& position )
{
CHECK_OBJECT();

    osg::Matrix m = mTransformNode->getMatrix();
    m.setTrans( position );
    mTransformNode->setMatrix( m );
}

WorldVertex
Object::GetPosition( void )
{
CHECK_OBJECT();

    return mTransformNode->getMatrix().getTrans();
}

/*****************************************************************************
 * Compute approximate radius of Object.
 *****************************************************************************/
fp
Object::ComputeRadius( void )
{
CHECK_OBJECT();

    // Must ensure child of switch node is enabled, or radius will be -1.
    const bool graphEnabled = GetGraph()->IfEnabled();
    GetGraph()->Enable( true );
      //const fp radius = GetRootNode()->computeBound().radius();
        const fp radius = GetRootNode()->getBound().radius();  // faster
        ASSERT( IfZombie() or radius > 0.0f );  // OSG could return -1
    GetGraph()->Enable( graphEnabled );  // restore
    return radius;
}

////////////////////////////////////////////////////////////////////////////////
///////////////////////////  Object : geometry  ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Scale an Object.
 *****************************************************************************/
void
Object::Scale( const Vector3& v )
{
CHECK_OBJECT();

    MatrixScale( *mTransformNode, v );
}

void
Object::Scale( const fp scale )
{
CHECK_OBJECT();

    Scale( Vector3( scale, scale, scale ) );
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////  Object : transformation  /////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Set matrix.
 *****************************************************************************/
void
Object::SetMatrix( const Matrix& matrix, eMatrixChange /* used by Dyna */ )
{
CHECK_OBJECT();

    mTransformNode->setMatrix( matrix );
}

/*****************************************************************************
 * Set rotation by quaternion.
 * Note: Quaternions don't define position.
 *****************************************************************************/
void
Object::SetQuaternion( const Quaternion& quat )
{
CHECK_OBJECT();

    // Reuse SetMatrix().  But Quaternions don't have a position.
    SetMatrix( Matrix( quat, GetPosition() ),
               Object::ROTATION );
}

Quaternion
Object::GetQuaternion( void )
{
CHECK_OBJECT();

    return Quaternion( GetMatrix() );  // discards position
}

/*****************************************************************************
 * Move Object along one of its local axis.
 *****************************************************************************/
void
Object::Translate( uint axis, fp inc )
{
CHECK_OBJECT();
ASSERT_AXIS3( axis );

    Matrix m = GetMatrix();
    MatrixTranslateLocal<>( m, axis, inc );
    SetMatrix( m, Object::TRANSLATION );
}

/*****************************************************************************
 * Translate Object by adding a 3D vector offset.
 *****************************************************************************/
void
Object::Translate( const Vector3& v )
{
CHECK_OBJECT();

    Matrix m = GetMatrix();
    MatrixTranslateLocal<>( m, XX, v[XX] );
    MatrixTranslateLocal<>( m, YY, v[YY] );
    MatrixTranslateLocal<>( m, ZZ, v[ZZ] );
    SetMatrix( m, Object::TRANSLATION );
}

/*****************************************************************************
 * Rotate Object around one of its local axis.
 *****************************************************************************/
void
Object::Rotate( uint axis, Radian radian )
{
CHECK_OBJECT();
ASSERT_AXIS3(axis);

    Matrix m = GetMatrix();
    MatrixRotateLocal<>( m, axis, radian );
    SetMatrix( m, Object::ROTATION );
}

////////////////////////////////////////////////////////////////////////////////
///////////////////  Object : collidable, collision  ///////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Set whether an Object is collidable.
 *****************************************************************************/
void
Object::SetCollidable( const bool collidable )
{
CHECK_OBJECT();

    // Has collidable flag changed?
    if ( collidable != mBits.mCollidable )
    {
        mBits.mCollidable = collidable;

        // Notify Collision to update its container of collidable objects.
        GET_COLLISION().SetCollidable( this, collidable );
    }
}

/*****************************************************************************
 * Set collision flag.
 * If COLLISION_FATAL then Object will zombify itself
 * which will broadcast the DestroyObject event.
 *****************************************************************************/
void
Object::SetCollision( const eCollision collision )
{
CHECK_OBJECT();

    mBits.mCollision = collision;

    if ( collision == COLLISION_FATAL )
        Zombify();
}

} // namespace object
